#include "AchievementManager.h"
using namespace AchievementSystem;

#include "BitmapFont.h"
#include "Camera.h"
#include "Time.h"
#include "../SGD Wrappers/CSGD_Direct3D.h"

#include "../TinyXML/tinyxml.h"

#pragma region Achievement Manager

// GetInstance
//	- returns a reference to the AchievementManager.
AchievementManager& AchievementManager::GetInstance(void)
{
	static AchievementManager instance;

	return instance;
}

// Constructor
AchievementManager::AchievementManager(void)
{
	fontID = BitmapFont::GetInstance()->LoadFont(string("XML\\fonts\\xolonium.xml"));

	displayPadding = 10;
	displayWidth = displayPadding << 1;
	displayHeight = displayPadding << 1;
	displayTime = 4.0f;
	displayTimer = 0.0f;
	displaySpeed = 180.0f;
	displayAchievement = false;
	displayName = "";

	displayRect.left = -displayWidth;
	displayRect.top = 50;
	displayRect.right = 0;
	displayRect.bottom = 50 + displayHeight;
}

// Destructor
AchievementManager::~AchievementManager(void)
{
	
}

// Author: Joshua Bramer
// Description:
//		- Sends achievement data to the AchievementManager to be processed.
// Parameters:
//		data		-	A structure containing data about the acheivement.
// Notes:
//		- The AchievementManager will look for an achievement waiting for the message.
//		   If one is found, the achievement will be updated. If none is found, the manager
//		   will simply ignore the event call.
void AchievementManager::SendEvent(AchievementData data)
{
	// Loop through all achievements.
	for(unsigned int i = 0; i < currentAchievements.size(); ++i)
	{
		// Has a matching achievement been found?
		if(currentAchievements[i].GetAchievementMessage() == data.GetEventMessage())
		{
			// Get a reference to the current achievement.
			Achievement& achievement = currentAchievements[i];

			// Add the data to the event by the event's specified method.
			switch (achievement.GetIncrementType())
			{
			case Achievement::IncrementType::ADD:
				achievement.SetValue(achievement.GetValue() + data.GetIntData());
				break;
			case Achievement::IncrementType::SUB:
				achievement.SetValue(achievement.GetValue() - data.GetIntData());
				break;
			case Achievement::IncrementType::SET:
				achievement.SetValue(data.GetIntData());
				break;
			default:
				break;
			}

			// Check the achievement for completion.
			if(CheckAchievementForCompletion(achievement) == true)
				DeclareAchievementCompleted(achievement);
		}
	}
}

// Author: Joshua Bramer
// Description:
//		- Sends achievement data to the AchievementManager to be processed.
// Parameters:
//		message		-	The name of the achievement.
//		data		-	An integer value used to update the achievement.
//						Example: If data is 1, then the Achievement Manager might add 1 to a counter.
// Notes:
//		- The AchievementManager will look for an achievement waiting for the message.
//		   If one is found, the achievement will be updated. If none is found, the manager
//		   will simply ignore the event call.
void AchievementManager::SendEvent(string message, int data)
{
	AchievementData achievementData;
	achievementData.SetData(data);
	achievementData.SetEventMessage(message);

	SendEvent(achievementData);
}

// Update
//	- Updates the Achievement Manager. This must be called for the user
//	  to receive achievement notifications.
//	- This does not need to be called for the actual achievement system
//	  to function.
void AchievementManager::Update(float deltaTime)
{
	if(displayAchievement == true)
	{
		// Move the display rectangle to the right.
		displayRect.left = (int)min(0.0f, displayRect.left + (displaySpeed * deltaTime));
		displayRect.right = (int)min(displayWidth + displayPadding, displayRect.right + (displaySpeed * deltaTime));

		// Update the timer.
		displayTimer += deltaTime;

		if(displayTimer >= displayTime)
			displayAchievement = false;
	}
	else
	{
		// Move the display rectangle to the left and off of the screen.
		displayRect.left = (int)max(-displayWidth - displayPadding, displayRect.left - (displaySpeed * deltaTime));
		displayRect.right = (int)max(0.0f, displayRect.right - (displaySpeed * deltaTime));
	}

	int i = 0;
}

// Render
//	- Render achievement notifications on screen for the user.
void AchievementManager::Render(void) const
{
	CSGD_Direct3D::GetInstance()->DrawRect(displayRect, D3DCOLOR_ARGB(255, 68, 163, 237));
	BitmapFont::GetInstance()->Print(displayName, (float)displayRect.left + (displayPadding >> 1),
		(float)displayRect.top + (displayPadding >> 1), fontID, D3DCOLOR_ARGB(255, 255, 255, 255));
}

//////////////////////////////////////////////////
// Accessors:

// GetAchievement
//	- Returns a pointer to the achievement with the specified name.
//	- If there is no achievement with the specified name, NULL is returned.
const Achievement* AchievementManager::GetAchievement(string name) const
{
	for(unsigned int i = 0; i < currentAchievements.size(); ++i)
		if(currentAchievements[i].GetName() == name)
			return &currentAchievements[i];
	return nullptr;
}

// CompleteAchievements
//	- Returns a constant reference to the vector of completed achievements.
const vector<Achievement>& AchievementManager::CompleteAchievements(void) const
{
	return completedAchievements;
}

// IncompleteAchievements
//	- Returns a constant reference to the vector of incomplete achievements.
const vector<Achievement>& AchievementManager::IncompleteAchievements(void) const
{
	return currentAchievements;
}

//////////////////////////////////////////////////
// Mutators:

// AddAchievement
//	- Adds the achievement to the collection of existing achievements.
void AchievementManager::AddAchievement(const Achievement& achievement)
{
	currentAchievements.push_back(achievement);
}

// RemoveAchievement
//	- Searches for an achievement with the specified name.
//	- If an achievement with that name is found, it is removed
//	  from the collection and the method returns TRUE.
//	- If an achievement is not found with the specified name,
//	  nothing is removed, and the method returns FALSE.
// NOTE
//	- Only the first matching achievement will be removed. If there
//	  are more achievements with the same name, they will not be
//	  removed without multiple calls to this method.
bool AchievementManager::RemoveAchievement(string name)
{
	for(unsigned int i = 0; i < currentAchievements.size(); ++i)
	{
		if(currentAchievements[i].GetName() == name)
		{
			currentAchievements.erase(currentAchievements.begin() + i);
			return true;
		}
	}
	return false;
}

// ClearAchievements
//	- Clears the collection of achievements.
void AchievementManager::ClearAchievements(void)
{
	currentAchievements.clear();
}

// LoadAchievements
//	- Opens the file with the specified name and loads in all of the
//	  achievements in the file.
//	- This method will add the achievements to any existing achievements.
//	- ClearAchievements must be called to empty the current achievement data.
//	- Returns TRUE if loading was successful.
bool AchievementManager::LoadAchievements(const char* fileName)
{
	// Ignore the request if the filename is null.
	if(fileName == nullptr)
		return false;

	TiXmlDocument document;
	bool successful = document.LoadFile(fileName);

	assert(successful == true && "AchievementManager.cpp (44) : The specified file cannot be loaded.");

	if(successful == false)
		return false;

	// Get the root element of the document.
	TiXmlElement* root = document.RootElement();
	if(root == nullptr)
		return false;

	TiXmlElement* achievementElement = root->FirstChildElement("achievement");
	if(achievementElement == nullptr)
		return false;

	// Loop through all achievement elements.
	while(achievementElement != nullptr)
	{
		string name;
		string message;
		string comparison;
		string increment;
		string description;
		int value;
		int target;

		name = achievementElement->Attribute("name");
		message = achievementElement->FirstChildElement("message")->Attribute("att");
		comparison = achievementElement->FirstChildElement("comparison")->Attribute("att");
		increment = achievementElement->FirstChildElement("increment")->Attribute("att");
		description = achievementElement->FirstChildElement("description")->GetText();

		achievementElement->FirstChildElement("value")->Attribute("att", &value);
		achievementElement->FirstChildElement("target")->Attribute("att", &target);

		Achievement achievement;

		achievement.SetName(name);
		achievement.SetMessage(message);
		achievement.SetComparisonType(Achievement::GetComparisonTypeFromString(comparison));
		achievement.SetIncrementType(Achievement::GetIncrementTypeFromString(increment));
		achievement.SetDescription(description);
		achievement.SetValue(value);
		achievement.SetTarget(target);

		

		currentAchievements.push_back(achievement);

		// Get the next sibling achievement element.
		achievementElement = achievementElement->NextSiblingElement("achievement");
	}

	return true;
}

// SetAchievementCompleted
//	- Tells the Achievement Manager that the achievement with the specified name
//	  has been completed.
// Note
//	- The Achievement Manager will not display this to the user.
void AchievementManager::SetAchievementCompleted(string name)
{
	for(unsigned int i = 0; i < currentAchievements.size(); ++i)
	{
		if(currentAchievements[i].GetName() == name)
		{
			completedAchievements.push_back(currentAchievements[i]);
			currentAchievements.erase(currentAchievements.begin() + i);
			break;
		}
	}
}

// SetAchievementProgress
//	- Sets the value of the specified achievement.
//	- If the set value completes the achievement, the achievement will be marked
//	  as complete. Note that the achievement completion will not be displayed to
//	  the player.
void AchievementManager::SetAchievementProgress(string name, int value)
{
	for(unsigned int i = 0; i < currentAchievements.size(); ++i)
	{
		if(currentAchievements[i].GetName() == name)
		{
			currentAchievements[i].SetValue(value);
			
			if(CheckAchievementForCompletion(currentAchievements[i]) == true)
				SetAchievementCompleted(currentAchievements[i].GetName());
		}
	}
}

// DeclareAchievementCompleted
//	- This is a private helper function for AchievementManager.
//	- This marks an achievement as complete, and displays the completion
//	  to the player.
void AchievementManager::DeclareAchievementCompleted(Achievement achievement)
{
	// Set the achievement as completed.
	SetAchievementCompleted(achievement.GetName());

	displayWidth = BitmapFont::GetInstance()->GetTextLength(achievement.GetName(), fontID) + (displayPadding << 1);
	displayHeight = BitmapFont::GetInstance()->GetTextHeight(achievement.GetName(), fontID) + (displayPadding << 1);

	displayRect.left = -displayWidth - displayPadding;
	displayRect.right = 0;
	displayRect.top = 50;
	displayRect.bottom = displayRect.top + displayHeight + displayPadding;

	displayName = achievement.GetName();
	displayAchievement = true;
	displayTimer = 0.0f;
}

// CheckAchievementForCompletion
//	- This is a private helper function for AchievementManager.
//	- This checks if the achievement is complete or not. If it is complete, it returns TRUE.
//	  Otherwise it returns FALSE.
bool AchievementManager::CheckAchievementForCompletion(Achievement achievement)
{
	switch (achievement.GetComparisonType())
	{
	case Achievement::ComparisonType::EqualTo:
		if(achievement.GetValue() == achievement.GetTarget())
			return true;
		break;
	case Achievement::ComparisonType::GreaterThan:
		if(achievement.GetValue() > achievement.GetTarget())
			return true;
		break;
	case Achievement::ComparisonType::GreaterThanEqualTo:
		if(achievement.GetValue() >= achievement.GetTarget())
			return true;
		break;
	case Achievement::ComparisonType::LessThan:
		if(achievement.GetValue() < achievement.GetTarget())
			return true;
		break;
	case Achievement::ComparisonType::LessThanEqualTo:
		if(achievement.GetValue() <= achievement.GetTarget())
			return true;
		break;
	case Achievement::ComparisonType::NotEqualTo:
		if(achievement.GetValue() != achievement.GetTarget())
			return true;
		break;
	default:
		return false;
		break;
	}

	return false;
}

#pragma endregion

#pragma region Achievement

// Constructor
Achievement::Achievement(void)
{
}

// Destructor
Achievement::~Achievement(void)
{
}

//////////////////////////////////////////////////
// Accessors:
string Achievement::GetName(void) const
{
	return this->name;
}
string Achievement::GetAchievementMessage(void) const
{
	return this->message;
}
string Achievement::GetDescription(void) const
{
	return description;
}
Achievement::ComparisonType Achievement::GetComparisonType(void) const
{
	return comparison;
}
Achievement::IncrementType Achievement::GetIncrementType(void) const
{
	return increment;
}
int Achievement::GetValue(void) const
{
	return value;
}
int Achievement::GetTarget(void) const
{
	return target;
}

//////////////////////////////////////////////////
// Mutators:
void Achievement::SetName(string name)
{
	this->name = name;
}
void Achievement::SetMessage(string message)
{
	this->message = message;
}
void Achievement::SetDescription(string description)
{
	this->description = description;
}
void Achievement::SetComparisonType(ComparisonType comparision)
{
	this->comparison = comparision;
}
void Achievement::SetIncrementType(IncrementType increment)
{
	this->increment = increment;
}
void Achievement::SetValue(int value)
{
	this->value = value;
}
void Achievement::SetTarget(int target)
{
	this->target = target;
}

//////////////////////////////////////////////////
// Static Helper Methods:
Achievement::ComparisonType Achievement::GetComparisonTypeFromString(string strType)
{
	if(strType == "LessThan")
		return LessThan;
	else if(strType == "GreaterThan")
		return GreaterThan;
	else if(strType == "EqualTo")
		return EqualTo;
	else if(strType == "LessThanEqualTo")
		return LessThanEqualTo;
	else if(strType == "GreaterThanEqualTo")
		return GreaterThanEqualTo;
	else if(strType == "NotEqualTo")
		return NotEqualTo;
	else
	{
		assert(false && "Achievement::GetComparisonTypeFromString(string strType) : The string type is not a recognized value.");
		return EqualTo;
	}
}
Achievement::IncrementType Achievement::GetIncrementTypeFromString(string strType)
{
	if(strType == "Add")
		return ADD;
	else if(strType == "Sub")
		return SUB;
	else if(strType == "Set")
		return SET;
	else if(strType == "None")
		return NONE;
	else
	{
		assert(false && "Acievement::GetIncrementTypeFromString(string strType) : The string type is not a recognized value.");
		return NONE;
	}
}

#pragma endregion

#pragma region AchievementData

void AchievementData::SetEventMessage(string msg)
{
	this->message = msg;
}
void AchievementData::SetEventMessage(const char* msg)
{
	this->message = msg;
}

#pragma endregion
